// SPDX-License-Identifier: GPL-2.0

/*
 * lombo_dummy.c - Common code for LomboTech SoCs
 *
 * Copyright (C) 2016-2018, LomboTech Co.Ltd.
 * Author: lomboswer <lomboswer@lombotech.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 */

#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <sound/soc.h>
#include <sound/pcm_params.h>

#include <mach/debug.h>

#undef  MOD_NAME
#define MOD_NAME "LOMBO_DUMMY"

#define AUDIO_PLL_48000S		(2 * 24576000)
#define AUDIO_PLL_44100S		(2 * 22579200)

struct lombo_priv {
	unsigned int i2s_master;
	unsigned int mclk[2];
};

/* func to set hw_params, including bitwith, sample rates */
static int dummy_hw_params(struct snd_pcm_substream *substream,
	struct snd_pcm_hw_params *params)
{
	int ret;
	int clk_rate, clk_id;
	struct snd_soc_pcm_runtime *rtd = substream->private_data;
	struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
	struct snd_soc_dai *codec_dai = rtd->codec_dai;
	struct lombo_priv *priv = snd_soc_card_get_drvdata(rtd->card);
	int sample_rate = params_rate(params);

	dev_info(rtd->card->dev, "samle rate %d\n", sample_rate);

	switch (sample_rate) {
	case 8000:
	case 16000:
	case 24000:
	case 32000:
	case 48000:
	case 96000:
	case 192000:
		clk_id	 = 0;
		clk_rate = AUDIO_PLL_48000S;
		break;
	case 11025:
	case 22050:
	case 44100:
	case 88200:
		clk_id	 = 1;
		clk_rate = AUDIO_PLL_44100S;
		break;
	default:
		return -EINVAL;
	}
	/* set the clk rate */
	ret = snd_soc_dai_set_sysclk(cpu_dai, clk_id,
		clk_rate, SND_SOC_CLOCK_OUT);
	if (ret < 0) {
		dev_err(cpu_dai->dev, "set sysclk error\n");
		return ret;
	}

	/* set the clk rate for codec */
	ret = snd_soc_dai_set_sysclk(codec_dai, 0,
		priv->mclk[clk_id], SND_SOC_CLOCK_IN);
	if (ret < 0) {
		dev_err(codec_dai->dev, "set codec mclk error\n");
		return ret;
	}

	/* set codec m/s mode, i2s data fmt */
	ret = snd_soc_dai_set_fmt(codec_dai, rtd->dai_link->dai_fmt);
	if (ret < 0)
		return ret;

	/* set i2s m/s mode, i2s data fmt */
	ret = snd_soc_dai_set_fmt(cpu_dai, rtd->dai_link->dai_fmt);
	if (ret < 0)
		return ret;

	/* for i2s master mode, we need to set the mclk div */
	if (priv->i2s_master) {
		int mclk_div = 0;

		mclk_div = clk_rate / priv->mclk[clk_id] - 1;
		pr_err("mclk_div %d\n", mclk_div);
		ret = snd_soc_dai_set_clkdiv(cpu_dai, 0, mclk_div);
		if (ret < 0)
			return ret;
	}

	return 0;
}

/*
 * dummy DAI operations.
 */
static struct snd_soc_ops dummy_soc_ops = {
	.hw_params = dummy_hw_params,
};

/* define dai_link */
static struct snd_soc_dai_link dummy_dai_link[] = {
	{
		.name		= "dummy_ac",
		.stream_name	= "dummy_audio",
		.codec_dai_name = "dummy_ac_dai",
		.dai_fmt	= SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF,
		.ops		= &dummy_soc_ops,
	}
};

/* define sound card */
static struct snd_soc_card snd_soc_dummy = {
	.name		= "dummy_card",
	.owner		= THIS_MODULE,
	.dai_link	= dummy_dai_link,
	.num_links	= ARRAY_SIZE(dummy_dai_link),
};

static const struct of_device_id dummy_of_match[] = {
	{ .compatible = "lombo, dummy-audio", },
	{},
};

MODULE_DEVICE_TABLE(of, dummy_of_match);

static int lombo_audio_probe(struct platform_device *pdev)
{
	int ret, i;
	struct lombo_priv *priv;
	struct snd_soc_card *card = &snd_soc_dummy;
	struct device_node *np = pdev->dev.of_node;

	card->dev = &pdev->dev;
	priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
	if (!priv)
		return -ENOMEM;
	ret = device_property_read_u32(&pdev->dev, "i2s_master",
				 &priv->i2s_master);
	if (ret)
		dev_err(&pdev->dev, "get i2s_master err!!\n");
	if (priv->i2s_master) {
		ret = device_property_read_u32_array(&pdev->dev, "mclk",
			priv->mclk, 2);
		if (ret)
			dev_err(&pdev->dev, "get mclk err!!\n");
	}
	for (i = 0; i < ARRAY_SIZE(dummy_dai_link); ++i) {
		dummy_dai_link[i].cpu_of_node =
			of_parse_phandle(np, "lombo,cpu_dai", i);
		if (!dummy_dai_link[i].cpu_of_node) {
			dev_err(&pdev->dev,
				"Property 'lombo,cpu_dai' missing or invalid\n");
			return -EINVAL;
		}
		dummy_dai_link[i].codec_of_node =
			of_parse_phandle(np, "lombo,codec", i);
		if (!dummy_dai_link[i].codec_of_node) {
			dev_err(&pdev->dev,
				"Property 'lombo,codec_dai' missing or invalid\n");
			return -EINVAL;
		}
		dummy_dai_link[i].platform_of_node =
			dummy_dai_link[i].cpu_of_node;
		if (priv->i2s_master)
			dummy_dai_link[i].dai_fmt |= SND_SOC_DAIFMT_CBS_CFS;
		else
			dummy_dai_link[i].dai_fmt |= SND_SOC_DAIFMT_CBM_CFM;
		pr_debug("parse cpudai %s, codec dai %s\n",
			dummy_dai_link[i].cpu_of_node->name,
			dummy_dai_link[i].codec_of_node->name);
	}

	/* set card priv data */
	snd_soc_card_set_drvdata(card, priv);

	ret = snd_soc_register_card(card);
	if (ret)
		dev_err(&pdev->dev, "snd_soc_register_card() failed:%d\n", ret);

	return ret;
}

static int lombo_audio_remove(struct platform_device *pdev)
{
	return snd_soc_unregister_card(&snd_soc_dummy);
}

static struct platform_driver lombo_audio_driver = {
	.driver		= {
		.name	= "lombo-audio-dummy",
		.of_match_table = of_match_ptr(dummy_of_match),
	},
	.probe		= lombo_audio_probe,
	.remove		= lombo_audio_remove,
};

module_platform_driver(lombo_audio_driver);

MODULE_AUTHOR("lomboswer <lomboswer@lombotech.com>");
MODULE_DESCRIPTION("ALSA SoC dummy");
MODULE_LICENSE("GPL");

